// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/logging.h"
#include "base/macros.h"
#include "media/base/audio_bus.h"
#include "media/base/audio_hash.h"
#include "media/base/fake_audio_render_callback.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace media {

static const int kChannelCount = 2;
static const int kFrameCount = 1024;
static const int kSampleRate = 48000;

class AudioHashTest : public testing::Test {
public:
    AudioHashTest()
        : bus_one_(AudioBus::Create(kChannelCount, kFrameCount))
        , bus_two_(AudioBus::Create(kChannelCount, kFrameCount))
        , fake_callback_(0.01, kSampleRate)
    {
        // Fill each channel in each bus with unique data.
        GenerateUniqueChannels(bus_one_.get());
        GenerateUniqueChannels(bus_two_.get());
    }

    void GenerateUniqueChannels(AudioBus* audio_bus)
    {
        // Use an AudioBus wrapper to avoid an extra memcpy when filling channels.
        std::unique_ptr<AudioBus> wrapped_bus = AudioBus::CreateWrapper(1);
        wrapped_bus->set_frames(audio_bus->frames());

        // Since FakeAudioRenderCallback generates only a single channel of unique
        // audio data, we need to fill each channel manually.
        for (int ch = 0; ch < audio_bus->channels(); ++ch) {
            wrapped_bus->SetChannelData(0, audio_bus->channel(ch));
            fake_callback_.Render(base::TimeDelta(), base::TimeTicks::Now(), 0,
                wrapped_bus.get());
        }
    }

    ~AudioHashTest() override { }

protected:
    std::unique_ptr<AudioBus> bus_one_;
    std::unique_ptr<AudioBus> bus_two_;
    FakeAudioRenderCallback fake_callback_;

    DISALLOW_COPY_AND_ASSIGN(AudioHashTest);
};

// Ensure the same data hashes the same.
TEST_F(AudioHashTest, Equivalence)
{
    AudioHash hash_one;
    hash_one.Update(bus_one_.get(), bus_one_->frames());

    AudioHash hash_two;
    hash_two.Update(bus_one_.get(), bus_one_->frames());

    EXPECT_EQ(hash_one.ToString(), hash_two.ToString());
}

// Ensure sample order matters to the hash.
TEST_F(AudioHashTest, SampleOrder)
{
    AudioHash original_hash;
    original_hash.Update(bus_one_.get(), bus_one_->frames());

    // Swap a sample in the bus.
    std::swap(bus_one_->channel(0)[0], bus_one_->channel(0)[1]);

    AudioHash swapped_hash;
    swapped_hash.Update(bus_one_.get(), bus_one_->frames());

    EXPECT_NE(original_hash.ToString(), swapped_hash.ToString());
}

// Ensure channel order matters to the hash.
TEST_F(AudioHashTest, ChannelOrder)
{
    AudioHash original_hash;
    original_hash.Update(bus_one_.get(), bus_one_->frames());

    // Reverse channel order for the same sample data.
    const int channels = bus_one_->channels();
    std::unique_ptr<AudioBus> swapped_ch_bus = AudioBus::CreateWrapper(channels);
    swapped_ch_bus->set_frames(bus_one_->frames());
    for (int i = channels - 1; i >= 0; --i)
        swapped_ch_bus->SetChannelData(channels - (i + 1), bus_one_->channel(i));

    AudioHash swapped_hash;
    swapped_hash.Update(swapped_ch_bus.get(), swapped_ch_bus->frames());

    EXPECT_NE(original_hash.ToString(), swapped_hash.ToString());
}

// Ensure bus order matters to the hash.
TEST_F(AudioHashTest, BusOrder)
{
    AudioHash original_hash;
    original_hash.Update(bus_one_.get(), bus_one_->frames());
    original_hash.Update(bus_two_.get(), bus_two_->frames());

    AudioHash reordered_hash;
    reordered_hash.Update(bus_two_.get(), bus_two_->frames());
    reordered_hash.Update(bus_one_.get(), bus_one_->frames());

    EXPECT_NE(original_hash.ToString(), reordered_hash.ToString());
}

// Ensure bus order matters to the hash even with empty buses.
TEST_F(AudioHashTest, EmptyBusOrder)
{
    bus_one_->Zero();
    bus_two_->Zero();

    AudioHash one_bus_hash;
    one_bus_hash.Update(bus_one_.get(), bus_one_->frames());

    AudioHash two_bus_hash;
    two_bus_hash.Update(bus_one_.get(), bus_one_->frames());
    two_bus_hash.Update(bus_two_.get(), bus_two_->frames());

    EXPECT_NE(one_bus_hash.ToString(), two_bus_hash.ToString());
}

// Where A = [0, n], ensure hash(A[0:n/2]), hash(A[n/2:n]) and hash(A) result
// in the same value.
TEST_F(AudioHashTest, HashIgnoresUpdateOrder)
{
    AudioHash full_hash;
    full_hash.Update(bus_one_.get(), bus_one_->frames());

    AudioHash half_hash;
    half_hash.Update(bus_one_.get(), bus_one_->frames() / 2);

    // Create a new bus representing the second half of |bus_one_|.
    const int half_frames = bus_one_->frames() / 2;
    const int channels = bus_one_->channels();
    std::unique_ptr<AudioBus> half_bus = AudioBus::CreateWrapper(channels);
    half_bus->set_frames(half_frames);
    for (int i = 0; i < channels; ++i)
        half_bus->SetChannelData(i, bus_one_->channel(i) + half_frames);

    half_hash.Update(half_bus.get(), half_bus->frames());
    EXPECT_EQ(full_hash.ToString(), half_hash.ToString());
}

// Ensure approximate hashes pass verification.
TEST_F(AudioHashTest, VerifySimilarHash)
{
    AudioHash hash_one;
    hash_one.Update(bus_one_.get(), bus_one_->frames());

    // Twiddle the values inside the first bus.
    float* channel = bus_one_->channel(0);
    for (int i = 0; i < bus_one_->frames(); i += bus_one_->frames() / 64)
        channel[i] += 0.0001f;

    AudioHash hash_two;
    hash_two.Update(bus_one_.get(), bus_one_->frames());

    EXPECT_EQ(hash_one.ToString(), hash_two.ToString());

    // Twiddle the values too much...
    for (int i = 0; i < bus_one_->frames(); ++i)
        channel[i] += 0.0001f;

    AudioHash hash_three;
    hash_three.Update(bus_one_.get(), bus_one_->frames());
    EXPECT_NE(hash_one.ToString(), hash_three.ToString());
}

} // namespace media
